﻿using System;
using System.Collections.Generic;
using System.Linq;
using CodeStash.Common.DataAccess.EntityFramework;
using CodeStash.Common.DataAccess.Repository;
using CodeStash.Common.DataAccess.UnitOfWork;
using CodeStash.Common.Enums;

namespace CodeStash.Utils
{
    public static class SearchUtils
    {
        /// <summary>
        /// Get the latest snippets to display on the "Home" page.
        /// </summary>
        /// <param name="unitOfWork">The unit of work to enrol this search in.</param>
        /// <param name="codeSnippetRepository">The code snippet repository.</param>
        /// <param name="pageSize">The number of public snippets to retrieve.</param>
        /// <returns>A populated list of public snippets.</returns>
        public static List<CodeSnippet> GetLatestHomePagePublicSnippets(
            IUnitOfWork unitOfWork,
            IRepository<CodeSnippet> codeSnippetRepository,
            int pageSize
            )
        {
            Func<CodeSnippet, bool> predicate = x => x.Visibility == (int)CodeSnippetVisibility.Public;

            return codeSnippetRepository.FindBy(predicate).Take(pageSize).ToList();
        }

        /// <summary>
        /// Get the latest snippets to display on the "Home" page.
        /// </summary>
        /// <param name="unitOfWork">The unit of work to enrol this search in.</param>
        /// <param name="codeSnippetRepository">The code snippet repository.</param>
        /// <param name="pageSize">The number of public snippets to retrieve.</param>
        /// <param name="lastSnippetId">The last snippet retrieved.</param>
        /// <returns>A populated list of public snippets.</returns>
        public static List<CodeSnippet> GetLatestPublicSnippets(
            IUnitOfWork unitOfWork,
            IRepository<CodeSnippet> codeSnippetRepository,
            int lastSnippetId,
            int pageSize
            )
        {
            Func<CodeSnippet, bool> predicate = x => x.Visibility == (int)CodeSnippetVisibility.Public && x.CodeSnippetId > lastSnippetId;

            return codeSnippetRepository.FindBy(predicate).Take(pageSize).ToList();
        }


        public static Tuple<int, List<CodeSnippet>> FilterByVisibility(
            IUnitOfWork unitOfWork, 
            IRepository<CreatedTeam> createdTeamRepository, 
            IRepository<CodeTag> codeTagRepository,
            IRepository<Language> languageRepository,
            IRepository<CodeSnippet> codeSnippetRepository,
            SearchType searchType, 
            string searchValue,
            int pageNo, 
            int pageSize, 
            bool takePagingIntoAccount,
            CodeSnippetVisibility visibility, 
            Guid userId, 
            string[] tags,
            bool includeRating,
            int rating)
        {
            List<Guid> teamUserIds = GetListOfUserIdsInSameTeamAsCurrentUser(unitOfWork, createdTeamRepository, userId);
            Tuple<int, List<CodeSnippet>> results = null;

            switch (visibility)
            {
                case CodeSnippetVisibility.JustMe:
                    switch (searchType)
                    {
                        case SearchType.ByKeyWord:
                            Func<CodeSnippet, bool> predicate1;
                            if(!includeRating)
                            {
                                predicate1 = x => (x.Description.ToLower().Contains(searchValue)
                                            || x.Title.ToLower().Contains(searchValue) 
                                            || x.ActualCode.ToLower().Contains(searchValue)) 
                                            && x.AspNetMembershipUserId == userId;
                            }
                            else
                            {
                                predicate1 = x => (x.Description.ToLower().Contains(searchValue)
                                            || x.Title.ToLower().Contains(searchValue) 
                                            || x.ActualCode.ToLower().Contains(searchValue)) 
                                            && x.AspNetMembershipUserId == userId 
                                            && x.Rating >= rating;
                            }
                            results = ApplyFilter(unitOfWork, codeTagRepository, languageRepository, 
                                codeSnippetRepository, predicate1, searchType, pageNo, pageSize,
                                takePagingIntoAccount, userId, tags, searchValue);
                            break;
                        case SearchType.ByLanguage:
                        case SearchType.ByTag:
                            Func<CodeSnippet, bool> predicate2;
                            if (!includeRating)
                            {
                                predicate2 = x => x.AspNetMembershipUserId == userId;
                            }
                            else
                            {
                                predicate2 = x => x.AspNetMembershipUserId == userId 
                                            && x.Rating >= rating;
                            }
                            results = ApplyFilter(unitOfWork, codeTagRepository, languageRepository, 
                                codeSnippetRepository, predicate2, searchType, pageNo, pageSize,
                                takePagingIntoAccount, userId, tags, searchValue);
                            break;
                    }
                    break;
                case CodeSnippetVisibility.Public:
                    switch (searchType)
                    {
                        case SearchType.ByKeyWord:
                            Func<CodeSnippet, bool> predicate1;
                            if (!includeRating)
                            {
                                predicate1 = x => (x.Description.ToLower().Contains(searchValue)
                                            || x.Title.ToLower().Contains(searchValue) 
                                            || x.ActualCode.ToLower().Contains(searchValue)) && x.Visibility == (int)CodeSnippetVisibility.Public;
                            }
                            else
                            {
                                predicate1 = x => (x.Description.ToLower().Contains(searchValue)
                                            || x.Title.ToLower().Contains(searchValue) 
                                            || x.ActualCode.ToLower().Contains(searchValue))
                                            && x.Rating >= rating && x.Visibility == (int)CodeSnippetVisibility.Public;
                            }
                            results = ApplyFilter(unitOfWork, codeTagRepository, languageRepository, 
                                codeSnippetRepository, predicate1, searchType, pageNo, pageSize, 
                                takePagingIntoAccount, userId, tags, searchValue);
                            break;
                        case SearchType.ByLanguage:
                        case SearchType.ByTag:
                            Func<CodeSnippet, bool> predicate2;
                            if (!includeRating)
                            {
                                predicate2 =  x=> x.Visibility == (int)CodeSnippetVisibility.Public;
                            }
                            else
                            {
                                predicate2 = x => x.Rating >= rating && x.Visibility == (int)CodeSnippetVisibility.Public;
                            }
                            results = ApplyFilter(unitOfWork, codeTagRepository, languageRepository,
                                codeSnippetRepository, predicate2, searchType, pageNo, pageSize, 
                                takePagingIntoAccount, userId, tags, searchValue);
                            break;
                    }
                    break;

                case CodeSnippetVisibility.MyTeam:
                    switch (searchType)
                    {
                        case SearchType.ByKeyWord:
                            Func<CodeSnippet, bool> predicate1;
                            if (!includeRating)
                            {
                                predicate1 = x => (x.Description.ToLower().Contains(searchValue)
                                            || x.Title.ToLower().Contains(searchValue) 
                                            || x.ActualCode.ToLower().Contains(searchValue))
                                            && teamUserIds.Contains(x.AspNetMembershipUserId);
                            }
                            else
                            {
                                predicate1 = x => (x.Description.ToLower().Contains(searchValue)
                                            || x.Title.ToLower().Contains(searchValue)
                                            || x.ActualCode.ToLower().Contains(searchValue))
                                            && teamUserIds.Contains(x.AspNetMembershipUserId)
                                            && x.Rating >= rating;
                            }
                            results = ApplyFilter(unitOfWork, codeTagRepository, languageRepository, 
                                codeSnippetRepository, predicate1, searchType, pageNo, pageSize, 
                                takePagingIntoAccount, userId, tags, searchValue);
                            break;
                        case SearchType.ByLanguage:
                        case SearchType.ByTag:
                            Func<CodeSnippet, bool> predicate2;
                            if (!includeRating)
                            {
                                predicate2 = x => teamUserIds.Contains(x.AspNetMembershipUserId);
                            }
                            else
                            {
                                predicate2 = x => teamUserIds.Contains(x.AspNetMembershipUserId) 
                                            && x.Rating >= rating;
                            }
                            results = ApplyFilter(unitOfWork, codeTagRepository, languageRepository, 
                                codeSnippetRepository, predicate2, searchType, pageNo, pageSize, 
                                takePagingIntoAccount, userId, tags, searchValue);
                            break;
                    }
                    break;

            }
            //return the pages/filtered CodeSnippets
            return results;
        }



        internal static List<Guid> GetListOfUserIdsInSameTeamAsCurrentUser(IUnitOfWork unitOfWork,
            IRepository<CreatedTeam> teamRepo, Guid userId)
        {
            //1. Get team(s) current user
            teamRepo.EnrolInUnitOfWork(unitOfWork);
            IEnumerable<CreatedTeam> usersTeams = teamRepo.FindBy(x => x.AspMembershipUserId == userId);
            List<Guid> teamUserIds = new List<Guid>();
            //2. Get all userIds for users who are in same team(s) as user
            foreach (CreatedTeam userTeam in usersTeams)
            {
                foreach (CreatedTeam sameTeamAsUser in teamRepo.FindBy(x => x.TeamId == userTeam.TeamId))
                {
                    //add in team member Ids to list
                    if (!teamUserIds.Contains(sameTeamAsUser.AspMembershipUserId))
                        teamUserIds.Add(sameTeamAsUser.AspMembershipUserId);
                }
            }
            return teamUserIds;
        }
            


        private static Tuple<int, List<CodeSnippet>> ApplyFilter(
            IUnitOfWork unitOfWork,
            IRepository<CodeTag> codeTagRepository,
            IRepository<Language> languageRepository,
            IRepository<CodeSnippet> codeSnippetRepository, 
            Func<CodeSnippet, bool> predicate,
            SearchType searchType, 
            int pageNo, 
            int pageSize, 
            bool takePagingIntoAccount,
            Guid userId, 
            string[] tags, 
            string searchValue)
        {

            searchValue = searchValue.ToLower();

            Tuple<int, List<CodeSnippet>> results = null;

            switch (searchType)
            {
                case SearchType.ByKeyWord:
                    codeSnippetRepository.EnrolInUnitOfWork(unitOfWork);
                    int totalCountOfSnippets = codeSnippetRepository.FindBy(predicate).Count();
                    List<CodeSnippet> snippets;
                    if (takePagingIntoAccount)
                    {
                        snippets = codeSnippetRepository.FindBy(predicate).Skip((pageNo - 1) * pageSize).Take(pageSize).ToList();
                    }
                    else
                    {
                        snippets = codeSnippetRepository.FindBy(predicate).Select(FetchSlimmedDownCodeSnippetForSearch).ToList();
                    }
                    results = new Tuple<int, List<CodeSnippet>>(totalCountOfSnippets, snippets);
                    break;
                case SearchType.ByTag:
                    results = DoFilterForTags(unitOfWork, codeTagRepository, codeSnippetRepository, predicate, pageNo, pageSize, takePagingIntoAccount, tags);
                    break;
                case SearchType.ByLanguage:
                    results = DoFilterForLanguage(unitOfWork, languageRepository, codeSnippetRepository, predicate, pageNo, pageSize, takePagingIntoAccount, searchValue);
                    break;
            }

            return results;

        }


        private static Tuple<int, List<CodeSnippet>> DoFilterForTags(
                IUnitOfWork unitOfWork,
                IRepository<CodeTag> codeTagRepository,
                IRepository<CodeSnippet> codeSnippetRepository,
                Func<CodeSnippet, bool> predicate,
                int pageNo,
                int pageSize,
                bool takePagingIntoAccount,
                string[] tags)
        {
            List<CodeSnippet> results = new List<CodeSnippet>();
            int totalCountOfSnippets = 0;

            if (tags.Any())
            {
                List<int> codeSnippetsIds = new List<int>();


                foreach (String tag in tags)
                {

                    codeTagRepository.EnrolInUnitOfWork(unitOfWork);
                    IEnumerable<int> snippetsForTag = codeTagRepository.FindBy(x => x.Description.ToLower().Contains(tag.ToLower())).Select(x => x.MatchingSnippetId);
                    foreach (int matchingTagSnippetId in snippetsForTag)
                    {
                        codeSnippetsIds.Add(matchingTagSnippetId);
                    }
                    //now apply rest of filter
                    codeSnippetRepository.EnrolInUnitOfWork(unitOfWork);
                    IEnumerable<CodeSnippet> codeSnippets = codeSnippetRepository
                        .FindBy(x => codeSnippetsIds.Contains(x.CodeSnippetId));
                    if (predicate == null)
                    {
                        totalCountOfSnippets = codeSnippets.Count();
                        if (takePagingIntoAccount)
                        {
                            results = codeSnippets.Skip((pageNo - 1) * pageSize).Take(pageSize).ToList();
                        }
                        else
                        {
                            results = codeSnippets.Select(FetchSlimmedDownCodeSnippetForSearch).ToList();
                        }
                    }
                    else
                    {

                        totalCountOfSnippets = codeSnippets.Where(predicate).Count();
                        if (takePagingIntoAccount)
                        {
                            results = codeSnippets.Where(predicate).Skip((pageNo - 1) * pageSize).Take(pageSize).ToList();
                        }
                        else
                        {
                            results = codeSnippets.Where(predicate).Select(FetchSlimmedDownCodeSnippetForSearch).ToList();
                        }
                    }
                }
            }

            return new Tuple<int, List<CodeSnippet>>(totalCountOfSnippets, results);
        }


        private static CodeSnippet FetchSlimmedDownCodeSnippetForSearch(CodeSnippet fullSnippet)
        {
            CodeSnippet snippet = new CodeSnippet();
            snippet.CodeSnippetId = fullSnippet.CodeSnippetId;
            snippet.Description = fullSnippet.Description;
            snippet.Title = fullSnippet.Title;
            return snippet;

        }


        private static Tuple<int, List<CodeSnippet>> DoFilterForLanguage(
            IUnitOfWork unitOfWork,
            IRepository<Language> languageRepository,
            IRepository<CodeSnippet> codeSnippetRepository,
            Func<CodeSnippet, bool> predicate,
            int pageNo, 
            int pageSize,
            bool takePagingIntoAccount,
            string searchValue)
        {
            List<CodeSnippet> results = new List<CodeSnippet>();
            int totalCountOfSnippets = 0;

            if (!String.IsNullOrEmpty(searchValue))
            {
                List<int> codeSnippetsIds = new List<int>();

                languageRepository.EnrolInUnitOfWork(unitOfWork);
                int languageId = (from p in languageRepository.FindAll() where p.Language1.ToLower() == searchValue.ToLower() select p).First().LanguageId;
                //now apply rest of filter
                codeSnippetRepository.EnrolInUnitOfWork(unitOfWork);

                IEnumerable<CodeSnippet> codeSnippets = codeSnippetRepository.FindBy(x => x.LanguageId == languageId);
                if (predicate == null)
                {
                    totalCountOfSnippets = codeSnippets.Count();
                    if (takePagingIntoAccount)
                    {
                        results = codeSnippets.Skip((pageNo - 1) * pageSize).Take(pageSize).ToList();
                    }
                    else
                    {
                        results = codeSnippets.Select(FetchSlimmedDownCodeSnippetForSearch).ToList();
                    }
                }
                else
                {
                    totalCountOfSnippets = codeSnippets.Where(predicate).Count();
                    if (takePagingIntoAccount)
                    {
                        results = codeSnippets.Where(predicate).Skip((pageNo - 1) * pageSize).Take(pageSize).ToList();
                    }
                    else
                    {
                        results = codeSnippets.Where(predicate).Select(FetchSlimmedDownCodeSnippetForSearch).ToList();
                    }
                }

            }

            return new Tuple<int, List<CodeSnippet>>(totalCountOfSnippets, results);
        }
    }
}